// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "skia/ext/analysis_canvas.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "third_party/skia/include/core/SkDraw.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "third_party/skia/include/core/SkShader.h"

namespace {

const int kNoLayer = -1;

bool ActsLikeClear(SkXfermode::Mode mode, unsigned src_alpha)
{
    switch (mode) {
    case SkXfermode::kClear_Mode:
        return true;
    case SkXfermode::kSrc_Mode:
    case SkXfermode::kSrcIn_Mode:
    case SkXfermode::kDstIn_Mode:
    case SkXfermode::kSrcOut_Mode:
    case SkXfermode::kDstATop_Mode:
        return src_alpha == 0;
    case SkXfermode::kDstOut_Mode:
        return src_alpha == 0xFF;
    default:
        return false;
    }
}

bool IsSolidColorPaint(const SkPaint& paint)
{
    SkXfermode::Mode xfermode;

    // getXfermode can return a NULL, but that is handled
    // gracefully by AsMode (NULL turns into kSrcOver mode).
    if (!SkXfermode::AsMode(paint.getXfermode(), &xfermode))
        return false;

    // Paint is solid color if the following holds:
    // - Alpha is 1.0, style is fill, and there are no special effects
    // - Xfer mode is either kSrc or kSrcOver (kSrcOver is equivalent
    //   to kSrc if source alpha is 1.0, which is already checked).
    return (paint.getAlpha() == 255 && !paint.getShader() && !paint.getLooper() && !paint.getMaskFilter() && !paint.getColorFilter() && !paint.getImageFilter() && paint.getStyle() == SkPaint::kFill_Style && (xfermode == SkXfermode::kSrc_Mode || xfermode == SkXfermode::kSrcOver_Mode));
}

// Returns true if the specified drawn_rect will cover the entire canvas, and
// that the canvas is not clipped (i.e. it covers ALL of the canvas).
bool IsFullQuad(SkCanvas* canvas, const SkRect& drawn_rect)
{
    if (!canvas->isClipRect())
        return false;

    SkIRect clip_irect;
    if (!canvas->getClipDeviceBounds(&clip_irect))
        return false;

    // if the clip is smaller than the canvas, we're partly clipped, so abort.
    if (!clip_irect.contains(SkIRect::MakeSize(canvas->getBaseLayerSize())))
        return false;

    const SkMatrix& matrix = canvas->getTotalMatrix();
    // If the transform results in a non-axis aligned
    // rect, then be conservative and return false.
    if (!matrix.rectStaysRect())
        return false;

    SkRect device_rect;
    matrix.mapRect(&device_rect, drawn_rect);
    SkRect clip_rect;
    clip_rect.set(clip_irect);
    return device_rect.contains(clip_rect);
}

} // namespace

namespace skia {

void AnalysisCanvas::SetForceNotSolid(bool flag)
{
    is_forced_not_solid_ = flag;
    if (is_forced_not_solid_)
        is_solid_color_ = false;
}

void AnalysisCanvas::SetForceNotTransparent(bool flag)
{
    is_forced_not_transparent_ = flag;
    if (is_forced_not_transparent_)
        is_transparent_ = false;
}

void AnalysisCanvas::onDrawPaint(const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawPaint");
    SkRect rect;
    if (getClipBounds(&rect))
        drawRect(rect, paint);
}

void AnalysisCanvas::onDrawPoints(SkCanvas::PointMode mode,
    size_t count,
    const SkPoint points[],
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawPoints");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawRect");
    // This recreates the early-exit logic in SkCanvas.cpp.
    SkRect scratch;
    if (paint.canComputeFastBounds() && quickReject(paint.computeFastBounds(rect, &scratch))) {
        TRACE_EVENT_INSTANT0("disabled-by-default-skia", "Quick reject.",
            TRACE_EVENT_SCOPE_THREAD);
        return;
    }

    // An extra no-op check SkCanvas.cpp doesn't do.
    if (paint.nothingToDraw()) {
        TRACE_EVENT_INSTANT0("disabled-by-default-skia", "Nothing to draw.",
            TRACE_EVENT_SCOPE_THREAD);
        return;
    }

    bool does_cover_canvas = IsFullQuad(this, rect);

    SkXfermode::Mode xfermode;
    SkXfermode::AsMode(paint.getXfermode(), &xfermode);

    // This canvas will become transparent if the following holds:
    // - The quad is a full tile quad
    // - We're not in "forced not transparent" mode
    // - Transfer mode is clear (0 color, 0 alpha)
    //
    // If the paint alpha is not 0, or if the transfrer mode is
    // not src, then this canvas will not be transparent.
    //
    // In all other cases, we keep the current transparent value
    if (does_cover_canvas && !is_forced_not_transparent_ && ActsLikeClear(xfermode, paint.getAlpha())) {
        is_transparent_ = true;
    } else if (paint.getAlpha() != 0 || xfermode != SkXfermode::kSrc_Mode) {
        is_transparent_ = false;
    }

    // This bitmap is solid if and only if the following holds.
    // Note that this might be overly conservative:
    // - We're not in "forced not solid" mode
    // - Paint is solid color
    // - The quad is a full tile quad
    if (!is_forced_not_solid_ && IsSolidColorPaint(paint) && does_cover_canvas) {
        is_solid_color_ = true;
        color_ = paint.getColor();
    } else {
        is_solid_color_ = false;
    }
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawOval");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawRRect(const SkRRect& rr, const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawRRect");
    // This should add the SkRRect to an SkPath, and call
    // drawPath, but since drawPath ignores the SkPath, just
    // do the same work here.
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawPath(const SkPath& path, const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawPath");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawBitmap(const SkBitmap& bitmap,
    SkScalar left,
    SkScalar top,
    const SkPaint*)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawBitmap");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawBitmapRect(const SkBitmap&,
    const SkRect* src,
    const SkRect& dst,
    const SkPaint* paint,
    SrcRectConstraint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawBitmapRect");
    // Call drawRect to determine transparency,
    // but reset solid color to false.
    SkPaint tmpPaint;
    if (!paint)
        paint = &tmpPaint;
    drawRect(dst, *paint);
    is_solid_color_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawBitmapNine(const SkBitmap& bitmap,
    const SkIRect& center,
    const SkRect& dst,
    const SkPaint* paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawBitmapNine");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawImage(const SkImage*,
    SkScalar left,
    SkScalar top,
    const SkPaint*)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawImage");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawImageRect(const SkImage*,
    const SkRect* src,
    const SkRect& dst,
    const SkPaint* paint,
    SrcRectConstraint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawImageRect");
    // Call drawRect to determine transparency,
    // but reset solid color to false.
    SkPaint tmpPaint;
    if (!paint)
        paint = &tmpPaint;
    drawRect(dst, *paint);
    is_solid_color_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawText(const void* text,
    size_t len,
    SkScalar x,
    SkScalar y,
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawText");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawPosText(const void* text,
    size_t byteLength,
    const SkPoint pos[],
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawPosText");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawPosTextH(const void* text,
    size_t byteLength,
    const SkScalar xpos[],
    SkScalar constY,
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawPosTextH");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawTextOnPath(const void* text,
    size_t len,
    const SkPath& path,
    const SkMatrix* matrix,
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawTextOnPath");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawTextBlob(const SkTextBlob* blob,
    SkScalar x,
    SkScalar y,
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawTextBlob");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawDRRect(const SkRRect& outer,
    const SkRRect& inner,
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawDRRect");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

void AnalysisCanvas::onDrawVertices(SkCanvas::VertexMode,
    int vertex_count,
    const SkPoint verts[],
    const SkPoint texs[],
    const SkColor colors[],
    SkXfermode* xmode,
    const uint16_t indices[],
    int index_count,
    const SkPaint& paint)
{
    TRACE_EVENT0("disabled-by-default-skia", "AnalysisCanvas::onDrawVertices");
    is_solid_color_ = false;
    is_transparent_ = false;
    ++draw_op_count_;
}

// Needed for now, since SkCanvas requires a bitmap, even if it is not backed
// by any pixels
static SkBitmap MakeEmptyBitmap(int width, int height)
{
    SkBitmap bitmap;
    bitmap.setInfo(SkImageInfo::MakeUnknown(width, height));
    return bitmap;
}

AnalysisCanvas::AnalysisCanvas(int width, int height)
    : INHERITED(MakeEmptyBitmap(width, height))
    , saved_stack_size_(0)
    , force_not_solid_stack_level_(kNoLayer)
    , force_not_transparent_stack_level_(kNoLayer)
    , is_forced_not_solid_(false)
    , is_forced_not_transparent_(false)
    , is_solid_color_(true)
    , color_(SK_ColorTRANSPARENT)
    , is_transparent_(true)
    , draw_op_count_(0)
{
}

AnalysisCanvas::~AnalysisCanvas() { }

bool AnalysisCanvas::GetColorIfSolid(SkColor* color) const
{
    if (is_transparent_) {
        *color = SK_ColorTRANSPARENT;
        return true;
    }
    if (is_solid_color_) {
        *color = color_;
        return true;
    }
    return false;
}

bool AnalysisCanvas::abort()
{
    // Early out as soon as we have more than one draw op.
    // TODO(vmpstr): Investigate if 1 is the correct metric here. We need to
    // balance the amount of time we spend analyzing vs how many tiles would be
    // solid if the number was higher.
    if (draw_op_count_ > 1) {
        TRACE_EVENT0("disabled-by-default-skia",
            "AnalysisCanvas::abort() -- aborting");
        // We have to reset solid/transparent state to false since we don't
        // know whether consequent operations will make this false.
        is_solid_color_ = false;
        is_transparent_ = false;
        return true;
    }
    return false;
}

void AnalysisCanvas::OnComplexClip()
{
    // complex clips can make our calls to IsFullQuad invalid (ie have false
    // positives). As a precaution, force the setting to be non-solid
    // and non-transparent until we pop this
    if (force_not_solid_stack_level_ == kNoLayer) {
        force_not_solid_stack_level_ = saved_stack_size_;
        SetForceNotSolid(true);
    }
    if (force_not_transparent_stack_level_ == kNoLayer) {
        force_not_transparent_stack_level_ = saved_stack_size_;
        SetForceNotTransparent(true);
    }
}

void AnalysisCanvas::onClipRect(const SkRect& rect,
    SkRegion::Op op,
    ClipEdgeStyle edge_style)
{
    INHERITED::onClipRect(rect, op, edge_style);
}

void AnalysisCanvas::onClipPath(const SkPath& path,
    SkRegion::Op op,
    ClipEdgeStyle edge_style)
{
    OnComplexClip();
    INHERITED::onClipRect(path.getBounds(), op, edge_style);
}

void AnalysisCanvas::onClipRRect(const SkRRect& rrect,
    SkRegion::Op op,
    ClipEdgeStyle edge_style)
{
    OnComplexClip();
    INHERITED::onClipRect(rrect.getBounds(), op, edge_style);
}

void AnalysisCanvas::onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op)
{
    const ClipEdgeStyle edge_style = kHard_ClipEdgeStyle;
    if (deviceRgn.isRect()) {
        onClipRect(SkRect::MakeFromIRect(deviceRgn.getBounds()), op, edge_style);
        return;
    }
    OnComplexClip();
    INHERITED::onClipRect(
        SkRect::MakeFromIRect(deviceRgn.getBounds()), op, edge_style);
}

void AnalysisCanvas::willSave()
{
    ++saved_stack_size_;
    INHERITED::willSave();
}

SkCanvas::SaveLayerStrategy AnalysisCanvas::getSaveLayerStrategy(
    const SaveLayerRec& rec)
{
    const SkPaint* paint = rec.fPaint;

    ++saved_stack_size_;

    SkIRect canvas_ibounds = SkIRect::MakeSize(this->getBaseLayerSize());
    SkRect canvas_bounds;
    canvas_bounds.set(canvas_ibounds);

    // If after we draw to the saved layer, we have to blend with the current
    // layer, then we can conservatively say that the canvas will not be of
    // solid color.
    if ((paint && !IsSolidColorPaint(*paint)) || (rec.fBounds && !rec.fBounds->contains(canvas_bounds))) {
        if (force_not_solid_stack_level_ == kNoLayer) {
            force_not_solid_stack_level_ = saved_stack_size_;
            SetForceNotSolid(true);
        }
    }

    // If after we draw to the save layer, we have to blend with the current
    // layer using any part of the current layer's alpha, then we can
    // conservatively say that the canvas will not be transparent.
    SkXfermode::Mode xfermode = SkXfermode::kSrc_Mode;
    if (paint)
        SkXfermode::AsMode(paint->getXfermode(), &xfermode);
    if (xfermode != SkXfermode::kDst_Mode) {
        if (force_not_transparent_stack_level_ == kNoLayer) {
            force_not_transparent_stack_level_ = saved_stack_size_;
            SetForceNotTransparent(true);
        }
    }

    INHERITED::getSaveLayerStrategy(rec);
    // Actually saving a layer here could cause a new bitmap to be created
    // and real rendering to occur.
    return kNoLayer_SaveLayerStrategy;
}

void AnalysisCanvas::willRestore()
{
    DCHECK(saved_stack_size_);
    if (saved_stack_size_) {
        --saved_stack_size_;
        if (saved_stack_size_ < force_not_solid_stack_level_) {
            SetForceNotSolid(false);
            force_not_solid_stack_level_ = kNoLayer;
        }
        if (saved_stack_size_ < force_not_transparent_stack_level_) {
            SetForceNotTransparent(false);
            force_not_transparent_stack_level_ = kNoLayer;
        }
    }

    INHERITED::willRestore();
}

} // namespace skia
